package com.yoyo.schedule.task_manager;

import com.yoyo.schedule.Addresss;
import com.yoyo.schedule.DateFormats;
import com.yoyo.schedule.TaskItemAndParameter;
import com.yoyo.schedule.expression.CronExpression;
import com.yoyo.schedule.strategy.IStrategyTask;
import com.yoyo.schedule.strategy.ScheduleFactory;
import com.yoyo.schedule.task_get.TaskListGetter;
import com.yoyo.schedule.task_manager.processor.IProcessor;
import com.yoyo.schedule.task_manager.processor.NotSleepProcessor;
import com.yoyo.schedule.task_manager.processor.SleepProcessor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.util.Date;
import java.util.List;
import java.util.Timer;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于模块的形式进行调度classloader.load(task)
 */
@Slf4j
@SuppressWarnings({"rawtypes", "unchecked"})
public abstract class AbstractScheduleManager implements IStrategyTask {

    private static int serialNumber = 0;

    @Setter
    @Getter
    protected int currentSerialNumber = 0;
    @Setter
    @Getter
    protected TaskType taskType;
    @Setter
    @Getter
    protected TaskHandler taskHandler;
    @Setter
    @Getter
    protected List<TaskItemAndParameter> taskItemAndParameterList = new CopyOnWriteArrayList<TaskItemAndParameter>();
    @Setter
    @Getter
    protected long lastUpdateTaskItemAndParameterListTime = 0;
    @Setter
    @Getter
    protected boolean needGetTaskItemIdAndParameterList = true;
    @Setter
    @Getter
    public IDataManager dataManager;
    @Setter
    @Getter
    protected String startErrorInfo = null;
    @Setter
    @Getter
    protected boolean isStopSchedule = false;
    @Setter
    @Getter
    protected Lock registerTaskHandlerLock = new ReentrantLock();
    @Setter
    @Getter
    protected boolean isLeaderServer = false;
    @Setter
    @Getter
    TaskListGetter taskListGetter;
    @Setter
    @Getter
    IProcessor processor;

    Statistics statistics = new Statistics();
    @Setter
    @Getter
    boolean isPauseSchedule = true;
    @Setter
    @Getter
    String pauseMessage = "";
    @Setter
    @Getter
    ScheduleFactory scheduleFactory;
    @Setter
    @Getter
    private Timer heartBeatTimer;

    AbstractScheduleManager(ScheduleFactory scheduleFactory, String baseTaskType, String ownSign, IDataManager dataManager) throws Exception {
        this.scheduleFactory = scheduleFactory;
        this.currentSerialNumber = serialNumber();
        this.dataManager = dataManager;
        this.taskType = this.dataManager.loadBaseTaskType(baseTaskType);
        log.info("create AbstractScheduleManager for taskType:" + baseTaskType);

        this.dataManager.clearExpireTaskTypeRunningInfo(baseTaskType, Addresss.getLocalHostAddress() + "OWN_SIGN", this.taskType.getExpireOwnSignInterval());

        Object bean = scheduleFactory.getBean(this.taskType.getDealBeanName());
        if (bean == null) {
            throw new Exception("SpringBean " + this.taskType.getDealBeanName() + " ");
        }
        if (bean instanceof TaskListGetter == false) {
            throw new Exception("SpringBean " + this.taskType.getDealBeanName() + "  TaskDeal");
        }
        this.taskListGetter = (TaskListGetter) bean;
        //
        if (this.taskType.getJudgeDeadInterval() < this.taskType.getHeartBeatRate() * 5) {
            throw new Exception("JudgeDeadInterval = "
                    + this.taskType.getJudgeDeadInterval()
                    + ",HeartBeatRate = " + this.taskType.getHeartBeatRate());
        }
        //
        this.taskHandler = TaskHandlers.createTaskHandler(this.dataManager, baseTaskType, ownSign, this.taskType.getThreadNumber());
        this.taskHandler.setMachineId(this.scheduleFactory.getMachineId());
        this.dataManager.registerTaskHandler(this.taskHandler);//?为什么关联
        this.heartBeatTimer = new Timer(this.taskHandler.getTaskType() + "-" + this.currentSerialNumber + "-HeartBeat");
        this.heartBeatTimer.schedule(new HeartBeatTimerTask(this),
                new java.util.Date(System.currentTimeMillis() + 500),
                this.taskType.getHeartBeatRate());
        initial();
    }

    private static synchronized int serialNumber() {
        return serialNumber++;
    }

    public abstract void initial() throws Exception;

    public abstract void refreshScheduleServerInfo() throws Exception;

    public abstract void assignScheduleTask() throws Exception;

    public abstract List<TaskItemAndParameter> getTaskItemAndParameterList();

    public abstract int getTaskItemCount();

    public String getTaskHandlerTaskType() {
        return this.taskHandler.getTaskType();
    }

    public void init(String strategyName, String taskParameter) {

    }

    public int getCurrentSerialNumber() {
        return this.currentSerialNumber;
    }


    public void clearTaskList() {
        try {
            this.taskItemAndParameterList.clear();
            if (this.processor != null) {
                this.processor.clearTaskList();
            }
        } finally {

            this.needGetTaskItemIdAndParameterList = true;
        }

    }

    public void registerTaskHandler() throws Exception {
        registerTaskHandlerLock.lock();
        try {
            if (this.isStopSchedule == true) {
                return;
            }

            if (startErrorInfo == null) {
                this.taskHandler.setDealInfoDesc(this.pauseMessage + ":");
            } else {
                this.taskHandler.setDealInfoDesc(startErrorInfo);
            }
            if (this.dataManager.refreshHeartBeatTimeAndVersion(this.taskHandler) == false) {
                this.clearTaskList();
                this.dataManager.registerTaskHandler(this.taskHandler);//new set
            }
        } finally {
            registerTaskHandlerLock.unlock();
        }
    }


    public void computeStartDateTime() throws Exception {


        boolean isRunNow = false;
        if (this.taskType.getPlanRunStartTime() == null) {
            isRunNow = true;
        } else {
            String planRunStartTime = this.taskType.getPlanRunStartTime();
            if (planRunStartTime.toLowerCase().startsWith("startrun:")) {
                isRunNow = true;
                planRunStartTime = planRunStartTime.substring("startrun:".length());
            }
            CronExpression cronExpressionForPlanRunStartTime = new CronExpression(planRunStartTime);
            Date nowDate = new Date(this.dataManager.zooKeeperCurrentTimeMillis());
            Date startTime = cronExpressionForPlanRunStartTime.getNextTimeAfter(nowDate);
            this.heartBeatTimer.schedule(
                    new PauseOrResumeTimerTask(this, this.heartBeatTimer,
                            PauseOrResumeTimerTask.type_resume, planRunStartTime),
                    startTime);
            this.taskHandler.setNextRunStartTime(DateFormats.transferDataToString(startTime));
            if (this.taskType.getPlanRunEndTime() == null
                    || this.taskType.getPlanRunEndTime().equals("-1")) {
                this.taskHandler.setNextRunEndTime("pause");
            } else {
                try {
                    String planRunEndTime = this.taskType.getPlanRunEndTime();
                    CronExpression cronExpressionForPlanRunEndTime = new CronExpression(planRunEndTime);
                    Date firstEndTime = cronExpressionForPlanRunEndTime.getNextTimeAfter(startTime);
                    Date nowEndTime = cronExpressionForPlanRunEndTime.getNextTimeAfter(nowDate);
                    if (!nowEndTime.equals(firstEndTime) && nowDate.before(nowEndTime)) {
                        isRunNow = true;
                        firstEndTime = nowEndTime;
                    }
                    this.heartBeatTimer.schedule(
                            new PauseOrResumeTimerTask(this, this.heartBeatTimer,
                                    PauseOrResumeTimerTask.type_pause, planRunEndTime),
                            firstEndTime);
                    this.taskHandler.setNextRunEndTime(DateFormats.transferDataToString(firstEndTime));
                } catch (Exception e) {
                    log.error("" + taskHandler.getServerId(), e);
                    throw new Exception("" + taskHandler.getServerId(), e);
                }
            }
        }
        if (isRunNow == true) {
            this.resume("");
        }
        this.registerTaskHandler();

    }


    public boolean isContinueWhenHasData() throws Exception {
        if (!isPauseWhenNotHasData()) {
            return true;
        } else {
            this.pause("");
            return false;
        }
    }

    public boolean isPauseWhenNotHasData() {

        if (this.taskItemAndParameterList.size() > 0 && this.taskType.getPlanRunStartTime() != null) {
            return this.taskType.getPlanRunEndTime() == null
                    || this.taskType.getPlanRunEndTime().equals("-1");
        } else {
            return false;
        }
    }

    //////////////////////////////////////////////////////////////
    public void pause(String message) throws Exception {
        if (this.isPauseSchedule == false) {
            this.isPauseSchedule = true;
            this.pauseMessage = message;
            if (log.isDebugEnabled()) {
                log.debug("" + this.taskHandler.getServerId() + ":");
            }
            if (this.processor != null) {
                this.processor.stopSchedule();
            }
            registerTaskHandler();
        }
    }


    public void resume(String message) throws Exception {
        if (this.isPauseSchedule == true) {
            if (log.isDebugEnabled()) {
                log.debug(":" + this.taskHandler.getServerId());
            }
            this.isPauseSchedule = false;
            this.pauseMessage = message;
            if (this.taskListGetter != null) {
                if (this.taskType.getProcessorType() != null &&
                        this.taskType.getProcessorType().equalsIgnoreCase("NOTSLEEP") == true) {
                    this.taskType.setProcessorType("NOTSLEEP");
                    this.processor = new NotSleepProcessor(this,
                            taskListGetter, this.statistics);
                } else {
                    this.processor = new SleepProcessor(this,
                            taskListGetter, this.statistics);
                    this.taskType.setProcessorType("SLEEP");
                }
            }
            registerTaskHandler();
        }
    }


    public void stop(String strategyName) throws Exception {
        this.isPauseSchedule = false;
        if (this.processor != null) {
            this.processor.stopSchedule();
        } else {
            this.unRegisterTaskHandler();
        }
    }


    public void unRegisterTaskHandler() throws Exception {
        registerTaskHandlerLock.lock();
        try {
            if (this.processor != null) {
                this.processor = null;
            }
            if (this.isPauseSchedule == true) {
                return;
            }
            this.isStopSchedule = true;
            this.heartBeatTimer.cancel();
            this.dataManager.unRegisterTaskHandler(
                    this.taskHandler.getTaskType(),
                    this.taskHandler.getServerId());
        } finally {
            registerTaskHandlerLock.unlock();
        }
    }

}

